////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef __WN_INTERLOCKED_INL__
#define __WN_INTERLOCKED_INL__

#include "WNCore/inc/WNBase.h"
#include "WNCore/inc/WNAssert.h"
#include "WNConcurrency/inc/WNInterlocked.h"

#ifdef _WN_MSVC
    #include <intrin.h>
#endif

__WN_INLINE WN_VOID* WNInterlockedCompareSwapPointer(WN_VOID* volatile* _destination, WN_VOID* _swap, WN_VOID* _compare) {
    WN_RELEASE_ASSERT(_destination != WN_NULL);
    WN_RELEASE_ASSERT(_swap != WN_NULL);
    WN_RELEASE_ASSERT(_compare != WN_NULL);

    #ifdef _WN_MSVC
        #ifdef _WN_32_BIT
            return((WN_VOID*)_InterlockedCompareExchange((volatile WN_ATOM_T*)_destination,
                                                         (WN_ATOM_T)_swap,
                                                         (WN_ATOM_T)_compare));
        #elif defined _WN_64_BIT
            return(_InterlockedCompareExchangePointer(_destination, _swap, _compare));
        #endif
    #elif defined _WN_GNUC
        return(__sync_val_compare_and_swap(_destination, _compare, _swap));
    #endif
}

__WN_INLINE WN_ATOM_T WNInterlockedCompareSwap(volatile WN_ATOM_T* _destination, WN_ATOM_T _swap, WN_ATOM_T _compare) {
    WN_RELEASE_ASSERT(_destination != WN_NULL);

    #ifdef _WN_MSVC
        #ifdef _WN_32_BIT
            return((WN_ATOM_T)_InterlockedCompareExchange(_destination, _swap, _compare));
        #elif defined _WN_64_BIT
            return((WN_ATOM_T)_InterlockedCompareExchange64(_destination, _swap, _compare));
        #endif
    #elif defined _WN_GNUC
        return(__sync_val_compare_and_swap(_destination, _compare, _swap));
    #endif
}

__WN_INLINE WN_BOOL WNInterlockedDoubleCompareSwap(volatile WN_ATOM_T* _destination, WN_ATOM_T* _swap, WN_ATOM_T* _compare) {
    #ifdef _WN_MSVC
        #ifdef _WN_32_BIT
            WN_INT64 originalCompare;
        #endif
    #elif defined _WN_GNUC
        WN_BYTE result;
    #endif

    WN_RELEASE_ASSERT(_destination != WN_NULL);
    WN_RELEASE_ASSERT(_swap != WN_NULL);
    WN_RELEASE_ASSERT(_compare != WN_NULL);

    #ifdef _WN_MSVC
        #ifdef _WN_32_BIT
            originalCompare = *(WN_INT64*)_compare;
            *(WN_INT64*)_compare = _InterlockedCompareExchange64((volatile WN_INT64*)_destination,
                                                                 *(WN_INT64*)_swap,
                                                                 *(WN_INT64*)_compare);

            return(*(WN_INT64*)_compare == originalCompare);
        #elif defined _WN_64_BIT
            return(_InterlockedCompareExchange128(_destination, *(_swap + 1), *_swap, _compare) == 1);
        #endif
    #elif defined _WN_GNUC
        #ifdef _WN_X86
            #ifdef _WN_32_BIT
                __asm__ __volatile__ (
                    "xchg %%esi, %%ebx;"    // Swap ESI and EBX
                    "lock;"                 // Make cmpxchg8b atomic
                    "cmpxchg8b %0;"         // On success cmpxchg8b sets ZF
                    "setz %3;"              // If ZF set, set cas_result to 1
                    "xchg %%ebx, %%esi;"    // Re-swap ESI and EBX
                    // Output
                    : "+m" (*(volatile WN_ATOM_T (*)[2]) _destination), "+a" (*_compare), "+d" (*(_compare + 1)), "=q" (result)
                    // Input
                    : "S" (*_swap), "c" (*(_swap + 1))
                    // Memory barrier
                    : "cc", "memory"
                );
            #elif defined _WN_64_BIT
                __asm__ __volatile__ (
                    "xchg %%rsi, %%rbx;"    // Swap RSI and RBX
                    "lock;"                 // Make cmpxchg16b atomic
                    "cmpxchg16b %0;"        // On success cmpxchg16b sets ZF
                    "setz %3;"              // If ZF set, set cas_result to 1
                    "xchg %%rbx, %%rsi;"    // Re-swap RSI and RBX
                    // Output
                    : "+m" (*(volatile WN_ATOM_T (*)[2]) _destination), "+a" (*_compare), "+d" (*(_compare + 1)), "=q" (result)
                    // Input
                    : "S" (*_swap), "c" (*(_swap + 1))
                    // Memory barrier
                    : "cc", "memory"
                );
            #endif
        #endif

        return(result == 1);
    #endif
}

__WN_INLINE WN_VOID* WNInterlockedSwapPointer(WN_VOID* volatile* _target, WN_VOID* _value) {
    #ifdef _WN_GNUC
        WN_VOID* result;
    #endif

    WN_RELEASE_ASSERT(_target != WN_NULL);

    #ifdef _WN_MSVC
        #ifdef _WN_32_BIT
            return((WN_VOID*)_InterlockedExchange((volatile WN_ATOM_T*)_target, (WN_ATOM_T)_value));
        #elif defined _WN_64_BIT
            return(_InterlockedExchangePointer(_target, _value));
        #endif
    #elif defined _WN_GNUC
        result = __sync_lock_test_and_set(_target, _value);

        __sync_synchronize();

        return(result);
    #endif
}

__WN_INLINE WN_ATOM_T WNInterlockedSwap(volatile WN_ATOM_T* _target, WN_ATOM_T _value) {
    #ifdef _WN_GNUC
        WN_ATOM_T result;
    #endif

    WN_RELEASE_ASSERT(_target != WN_NULL);

    #ifdef _WN_MSVC
        #ifdef _WN_32_BIT
            return((WN_ATOM_T)_InterlockedExchange(_target, _value));
        #elif defined _WN_64_BIT
            return((WN_ATOM_T)_InterlockedExchange64(_target, _value));
        #endif
    #elif defined _WN_GNUC
        result = __sync_lock_test_and_set(_target, _value);

        __sync_synchronize();

        return(result);
    #endif
}

__WN_INLINE WN_ATOM_T WNInterlockedIncrement(volatile WN_ATOM_T* _value) {
    WN_RELEASE_ASSERT(_value != WN_NULL);

    #ifdef _WN_MSVC
        #ifdef _WN_32_BIT
            return((WN_ATOM_T)_InterlockedIncrement(_value));
        #elif defined _WN_64_BIT
            return((WN_ATOM_T)_InterlockedIncrement64(_value));
        #endif
    #elif defined _WN_GNUC
        return((WN_ATOM_T)__sync_add_and_fetch(_value, 1));
    #endif
}

__WN_INLINE WN_ATOM_T WNInterlockedDecrement(volatile WN_ATOM_T* _value) {
    WN_RELEASE_ASSERT(_value != WN_NULL);

    #ifdef _WN_MSVC
        #ifdef _WN_32_BIT
            return((WN_ATOM_T)_InterlockedDecrement(_value));
        #elif defined _WN_64_BIT
            return((WN_ATOM_T)_InterlockedDecrement64(_value));
        #endif
    #elif defined _WN_GNUC
        return((WN_ATOM_T)__sync_sub_and_fetch(_value, 1));
    #endif
}

#endif // __WN_INTERLOCKED_INL__